
package net.sourceforge.retroweaver.runtime.java.lang.annotation;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

/**
 * The implementation of the Annotation interface, which gets returned from various points
 * in java.lang.Class and java.lang.reflect.* classes.
 *
 * @author Toby Reyelts
 * Date: Feb 20, 2005
 * Time: 11:37:18 PM
 */
public class AnnotationImpl implements InvocationHandler, Annotation {

  private final Class<? extends Annotation> annotationType_;
  private final Map<String,Object> attributes;

  /**
   * Called from generated bytecode to instantiate a new Annotation.
   * Specifically, a new dynamic proxy is created with a type annotationType,
   * and this class as the InvocationHandler.
   *
   * @param annotationType - The Annotation class that this AnnotationImpl should represent
   * @param attributes - The attributes for the Annotation
   *
   * For example, for @Persistable, Persistable.class
   */
  public static Annotation createAnnotation(final Class<? extends Annotation> annotationType, final Map<String,Object> attributes) {
    try {
      return (Annotation) Proxy.newProxyInstance(AnnotationImpl.class.getClassLoader(), new Class[] { Annotation.class, annotationType }, new AnnotationImpl(annotationType, attributes)); 
    }
    catch (IllegalArgumentException e) {
      throw new AnnotationFormatError("Unexpected exception while trying to create an annotation of type: " + annotationType, e);
    }
  }

  private AnnotationImpl(final Class<? extends Annotation> annotationType, final Map<String,Object> attributes) {
    this.attributes = new HashMap<String,Object>(attributes);
    this.annotationType_ = annotationType;
  }

  private static final Method cloneMethod;

  static {
	try {
		cloneMethod = Object.class.getDeclaredMethod("clone", new Class[0]);
		cloneMethod.setAccessible(true);
	} catch (NoSuchMethodException e) {
		throw new RuntimeException(e.getMessage());
	}
  }

  public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
    // If it's declared in the annotationType, it's an attribute
    if (method.getDeclaringClass().equals(annotationType_)) {
        final String name = method.getName();
    	if (attributes.containsKey(name)) {
    		Object o = attributes.get(name);
    		if (o == null) {
    			return null;
    		} else if (o.getClass().isArray()) {
    			o = cloneMethod.invoke(o);
    		}
    		return o;
    	}
    	throw new IncompleteAnnotationException(annotationType_, name);
    }

    // Otherwise it's something we handle innately
    return method.invoke(this, args);
  }

  // Returns the annotation type of this annotation.
  public Class<? extends Annotation> annotationType() {
    return annotationType_;
  }

  // Returns true if the specified object represents an annotation that is logically equivalent to this one.
  public boolean equals(final Object obj) {
    return (obj instanceof AnnotationImpl && ((AnnotationImpl) obj).annotationType_ == annotationType_);
  }

  // Returns the hash code of this annotation, as defined below:
  public int hashCode() {
    // TODO: Implement this according to spec.
    //System.out.println("Warning: Annotation.hashCode() has yet to be implemented according to spec.");
    //return annotationType.hashCode();
	  return toString().hashCode();
  }

  // Returns a string representation of this annotation.
  public String toString() {
    // TODO: Implement this according to spec.

	  final StringBuilder msg = new StringBuilder();
	  msg.append('@').append(annotationType_.getName()).append('('); 
	  boolean first = true;
	  for (Map.Entry<String, Object> e: attributes.entrySet()) {
		  if (first) {
			  first = false;
		  } else {
			  msg.append(", ");
		  }
		  msg.append(e.getKey()).append('=');
		  final Object o = e.getValue();
		  if (o.getClass().isArray()) {
			  msg.append(java.util.Arrays.deepToString((Object[])o));
		  } else {
			  msg.append(o);
		  }
	  }
	  msg.append(')');
	  return msg.toString();
  }

}

